//
//  ADLocalStorage.m
//  ADKit
//
//  Created by duzexu on 2018/4/12.
//

#import "ADLocalStorage.h"
#import "NSFileManager+ADUtil.h"
#import <objc/runtime.h>

@interface ADLocalStorage ()
@property (strong, nonatomic) NSMutableDictionary *mapping;
@property (nonatomic, copy) NSString *path;
@end

@implementation ADLocalStorage

enum TypeEncodings {
    Char                = 'c',
    Bool                = 'B',
    Short               = 's',
    Int                 = 'i',
    Long                = 'l',
    LongLong            = 'q',
    UnsignedChar        = 'C',
    UnsignedShort       = 'S',
    UnsignedInt         = 'I',
    UnsignedLong        = 'L',
    UnsignedLongLong    = 'Q',
    Float               = 'f',
    Double              = 'd',
    Object              = '@'
};

- (NSString *)defaultsKeyForPropertyNamed:(char const *)propertyName {
    NSString *key = [NSString stringWithFormat:@"%s", propertyName];
    return key;
}

- (NSString *)defaultsKeyForSelector:(SEL)selector {
    NSString *key = [self.mapping objectForKey:NSStringFromSelector(selector)];
    return [self.path stringByAppendingPathComponent:key];
}

static long long longLongGetter(ADLocalStorage *self, SEL _cmd) {
    return 0;
}

static void longLongSetter(ADLocalStorage *self, SEL _cmd, long long value) {
    NSAssert(0, @"use ADUserDefaults instead");
}

static bool boolGetter(ADLocalStorage *self, SEL _cmd) {
    return NO;
}

static void boolSetter(ADLocalStorage *self, SEL _cmd, bool value) {
    NSAssert(0, @"use ADUserDefaults instead");
}

static int integerGetter(ADLocalStorage *self, SEL _cmd) {
    return 0;
}

static void integerSetter(ADLocalStorage *self, SEL _cmd, int value) {
    NSAssert(0, @"use ADUserDefaults instead");
}

static float floatGetter(ADLocalStorage *self, SEL _cmd) {
    return 0;
}

static void floatSetter(ADLocalStorage *self, SEL _cmd, float value) {
    NSAssert(0, @"use ADUserDefaults instead");
}

static double doubleGetter(ADLocalStorage *self, SEL _cmd) {
    return 0;
}

static void doubleSetter(ADLocalStorage *self, SEL _cmd, double value) {
    NSAssert(0, @"use ADUserDefaults instead");
}

static id objectGetter(ADLocalStorage *self, SEL _cmd) {
    NSString *path = [self defaultsKeyForSelector:_cmd];
    id object = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    return object;
}

static void objectSetter(ADLocalStorage *self, SEL _cmd, id object) {
    NSString *path = [self defaultsKeyForSelector:_cmd];
    if (object) {
        if ([self useKeyedArchiver:object]) {
            [NSKeyedArchiver archiveRootObject:object toFile:path];
        }else{
            NSAssert(0, @"use ADUserDefaults instead");
        }
    } else {
        [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
    }
}

- (BOOL)useKeyedArchiver:(id)object {
    if ([object isKindOfClass:NSString.class]) {
        return NO;
    }
    if ([object isKindOfClass:NSNumber.class]) {
        return NO;
    }
    if ([object isKindOfClass:NSDate.class]) {
        return NO;
    }
    return YES;
}

#pragma mark - Begin

static ADLocalStorage *sharedInstance = nil;
+ (instancetype)storageWithPath:(NSString *)path {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] initWithPath:path];
    });
    return sharedInstance;
}

+ (instancetype)defaultStorage {
    return sharedInstance;
}

- (instancetype)initWithPath:(NSString *)path {
    self = [super init];
    if (self) {
        _path = [path copy];
        if (![NSFileManager ad_isDirectoryExist:path]) {
            [NSFileManager ad_createDirectoryAtPath:path];
        }
        [self generateAccessorMethods];
    }
    return self;
}

- (void)generateAccessorMethods {
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    self.mapping = [NSMutableDictionary dictionary];
    
    for (int i = 0; i < count; ++i) {
        objc_property_t property = properties[i];
        const char *name = property_getName(property);
        const char *attributes = property_getAttributes(property);
        
        char *getter = strstr(attributes, ",G");
        if (getter) {
            getter = strdup(getter + 2);
            getter = strsep(&getter, ",");
        } else {
            getter = strdup(name);
        }
        SEL getterSel = sel_registerName(getter);
        free(getter);
        
        char *setter = strstr(attributes, ",S");
        if (setter) {
            setter = strdup(setter + 2);
            setter = strsep(&setter, ",");
        } else {
            asprintf(&setter, "set%c%s:", toupper(name[0]), name + 1);
        }
        SEL setterSel = sel_registerName(setter);
        free(setter);
        
        NSString *key = [self defaultsKeyForPropertyNamed:name];
        [self.mapping setValue:key forKey:NSStringFromSelector(getterSel)];
        [self.mapping setValue:key forKey:NSStringFromSelector(setterSel)];
        
        IMP getterImp = NULL;
        IMP setterImp = NULL;
        char type = attributes[1];
        switch (type) {
            case Short:
            case Long:
            case LongLong:
            case UnsignedChar:
            case UnsignedShort:
            case UnsignedInt:
            case UnsignedLong:
            case UnsignedLongLong:
                getterImp = (IMP)longLongGetter;
                setterImp = (IMP)longLongSetter;
                break;
                
            case Bool:
            case Char:
                getterImp = (IMP)boolGetter;
                setterImp = (IMP)boolSetter;
                break;
                
            case Int:
                getterImp = (IMP)integerGetter;
                setterImp = (IMP)integerSetter;
                break;
                
            case Float:
                getterImp = (IMP)floatGetter;
                setterImp = (IMP)floatSetter;
                break;
                
            case Double:
                getterImp = (IMP)doubleGetter;
                setterImp = (IMP)doubleSetter;
                break;
                
            case Object:
                getterImp = (IMP)objectGetter;
                setterImp = (IMP)objectSetter;
                break;
                
            default:
                free(properties);
                [NSException raise:NSInternalInconsistencyException format:@"Unsupported type of property \"%s\" in class %@", name, self];
                break;
        }
        
        char types[5];
        
        snprintf(types, 4, "%c@:", type);
        class_addMethod([self class], getterSel, getterImp, types);
        
        snprintf(types, 5, "v@:%c", type);
        class_addMethod([self class], setterSel, setterImp, types);
    }
    
    free(properties);
}

@end
